/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

// Helper Function
// Returns true if the key and value agree with the select condition,
// and false otherwise
bool check_cond(int key, string value, SelCond cond)
{
	int    diff;		// Difference in tuple values
	
	// compute the difference between the tuple value and 
	// the condition value
	switch (cond.attr) 
	{
		case 1:
			diff = key - atoi(cond.value);
			break;
		case 2:
			diff = strcmp(value.c_str(), cond.value);
			break;
	}
	
	// skip the tuple if any condition is not met
	switch (cond.comp) 
	{
		case SelCond::EQ:
			if (diff != 0) 
				return false;
			break;
		case SelCond::NE:
			if (diff == 0) 
				return false;
			break;
		case SelCond::GT:
			if (diff <= 0)
				return false;
			break;
		case SelCond::LT:
			if (diff >= 0)
				return false;
			break;
		case SelCond::GE:
			if (diff < 0)
				return false;
			break;
		case SelCond::LE:
			if (diff > 0)
				return false;
			break;
	}
	
	return true;
}


RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
	RecordFile rf;	// RecordFile containing the table
	RC     rc;		// Function Return Values
	
	// Open the table file and check for success
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	}
	
	// Open the index file and check for success
	BTreeIndex index_file;
	bool index_exists = false;
	if(index_file.open(table + ".idx", 'r') == 0)
	{
		index_exists = true;
	}

	RecordId   rid;  // record cursor for table scanning
	
	int    key; 
	string value;
	int    count = 0;	// Number of matching tuples, for count(*)
	int    diff;		// Difference in tuple values
	
	// Minimum condition variables
	SelCond* key_MIN = NULL;	// Minimum key
	SelCond* key_MAX = NULL;	// Maximum key
	SelCond* key_EQU = NULL;	// Key equal
	vector<SelCond*> key_NEQ;	// Key not equal
	bool key_exists = false;	// Key condition exists
	
	SelCond* val_MIN = NULL;	// Minimum value
	SelCond* val_MAX = NULL;	// Maximum value
	SelCond* val_EQU = NULL;	// Value equal
	vector<SelCond*> val_NEQ;	// Value not equal
	bool val_exists = false;	// Value condition exists
	vector<SelCond> reduced_key_cond;
	vector<SelCond> reduced_val_cond;
		
	// Reduce all conditions down to the minimum equivalent set
	for(int k = 0; k < cond.size(); k++)
	{
		int temp = atoi(cond[k].value);
		
		// Key related condition
		if(cond[k].attr == 1)
		{
			switch(cond[k].comp)
			{
				// Equal condition (only one)
				case SelCond::EQ:
					if(key_EQU == NULL)
					{
						key_EQU = (SelCond*) &cond[k];
						key_exists = true;
					}
					else
						goto exit_select;
					break;
					
				// Not equal condition
				case SelCond::NE:
					key_NEQ.push_back( (SelCond*) &cond[k] );
					key_exists = true;
					break;
					
				// Minimum condition (Greater Than / Greater Than or Equal)
				case SelCond::GE:
					temp--;
				case SelCond::GT:
					if(key_MIN == NULL || atoi(key_MIN->value) < temp)
					{
						key_MIN = (SelCond*) &cond[k];
						key_exists = true;
					}
					break;

				// Maximum condition (Less Than / Less Than or Equal)
				case SelCond::LE:
					temp++;
				case SelCond::LT:
					if(key_MAX == NULL || atoi(key_MAX->value) > temp)
					{
						key_MAX = (SelCond*) &cond[k];
						key_exists = true;
					}
					break;
			}
		}
		
		// Value related condition
		else
		{
			switch(cond[k].comp)
			{
				// Equal condition (only one)
				case SelCond::EQ:
					if(val_EQU == NULL)
					{
						val_EQU = (SelCond*) &cond[k];
						val_exists = true;
					}
					else
						goto exit_select;
					break;
					
				// Not equal condition
				case SelCond::NE:
					val_NEQ.push_back( (SelCond*) &cond[k] );
					val_exists = true;
					break;
					
				// Minimum condition (Greater Than / Greater Than or Equal)
				case SelCond::GE:
					temp--;
				case SelCond::GT:
					if(val_MIN == NULL || atoi(val_MIN->value) < temp)
					{
						val_MIN = (SelCond*) &cond[k];
						val_exists = true;
					}
					break;
					
				// Maximum condition (Less Than / Less Than or Equal)
				case SelCond::LE:
					temp++;
				case SelCond::LT:
					if(val_MAX == NULL || atoi(val_MAX->value) > temp)
					{
						val_MAX = (SelCond*) &cond[k];
						val_exists = true;
					}
					break;
			}
		}
	}
	
	
	// Build a reduced condition vector
	if(key_exists)
	{
		if(key_MAX != NULL)
			reduced_key_cond.push_back(*key_MAX);
		if(key_MIN != NULL)
			reduced_key_cond.push_back(*key_MIN);
		if(key_EQU != NULL)
			reduced_key_cond.push_back(*key_EQU);
		for(int k = 0; k < key_NEQ.size(); k++)
			reduced_key_cond.push_back(*key_NEQ[k]);
			
	}
	if(val_exists)
	{
		if(val_MAX != NULL)
			reduced_val_cond.push_back(*val_MAX);
		if(val_MIN != NULL)
			reduced_val_cond.push_back(*val_MIN);
		if(val_EQU != NULL)
			reduced_val_cond.push_back(*val_EQU);
		for(int k = 0; k < val_NEQ.size(); k++)
			reduced_val_cond.push_back(*val_NEQ[k]);
	}
	
	count = 0;
	
	// The conditions for using a B+ tree are:
	//	- An index must exist
	//  - Keys are important OR values are unimportant
	//		- Keys are important if key conditions exist
	//		- Values are unimportant if value conditions don't exist, and
	//		  we never need to print out the values
	//			- This occurs when we only print out the key or the count
	if(index_exists && (  key_exists || 
						((attr == 1 || attr == 4) && !val_exists) ) )
	{
		IndexCursor cursor;
		
		// Check to see if we just need to do an equal key query
		if(key_EQU != NULL)
		{
			// Locate the first key >= to the condition
			rc = index_file.locate(atoi(key_EQU->value), cursor);
			
			// If such a key existed and there were no problems
			if(rc == 0) 
			{
				// Read out the rid of the located entry
				index_file.readForward(cursor, key, rid);
				
				// Check the key is actually equal
				if(atoi(key_EQU->value) == key) 
				{
					
					// Read the actual tuple only if we need the values
					// This only occurs if we need to output the values,
					// or if there are value-related conditions
					if(attr == 2 || attr == 3 || reduced_val_cond.size() > 0)
					{
						if ((rc = rf.read(rid, key, value)) < 0) 
						{
							fprintf(stderr, 
									"Error: while reading a tuple from table %s\n", 
									table.c_str());
							goto exit_select;
						}
					}
					
					// Scan condition vectors to see if conditions are met
					bool cond_met = true;
					for (unsigned i = 0; i < reduced_key_cond.size(); i++) 
					{
						if(check_cond(key, value, reduced_key_cond[i]) == false) 
						{
							cond_met = false;
							break;
						}
					}
					for (unsigned i = 0; i < reduced_val_cond.size(); i++) 
					{
						if(check_cond(key, value, reduced_val_cond[i]) == false) 
						{
							cond_met = false;
							break;
						}
					}
					
					// If all the conditions are true, output the value
					if (cond_met)
					{
						// print the tuple based on what output cols are wanted
						switch (attr) 
						{
							case 1:  // SELECT key
								fprintf(stdout, "%d\n", key);
								break;
							case 2:  // SELECT value
								fprintf(stdout, "%s\n", value.c_str());
								break;
							case 3:  // SELECT *
								fprintf(stdout, "%d '%s'\n", key, value.c_str());
								break;
						}
						count++;
					}
					// Otherwise no valid key that matches
				}
			}
			
		}
		
		// Otherwise, do read forwards from the minimum to the maximum
		// key values and check conditions
		else
		{
			// Locate the first key
			if(key_MIN != NULL) 
			{
				rc = index_file.locate(atoi(key_MIN->value), cursor);
			}
			// No min key means start at the very beginning of the index
			else
				rc = index_file.locate(0, cursor);
			
			// If there's an error
			if(rc != 0) {
				goto exit_select;
			}
			
			// Read out the value of the located entry
			RC rfRC;
			while ((rfRC = index_file.readForward(cursor, key, rid)) == 0 || 
				   (rfRC == RC_END_OF_TREE)) 
			{
				// Check the greater than condition for keys to stop
				if(key_MAX != NULL) 
				{
					if(key_MAX->comp == SelCond::LT && 
					   key >= atoi(key_MAX->value))
						break;
					else if(key_MAX->comp == SelCond::LE && 
							key > atoi(key_MAX->value))
						break;
				}
				
				// Read the tuple only if we need the value
				// This occurs only if we need to output the values,
				// or if there are value-related conditions
				if(attr == 2 || attr == 3 || reduced_val_cond.size() > 0)
				{
					if ((rc = rf.read(rid, key, value)) < 0) 
					{
						fprintf(stderr, 
								"Error: while reading a tuple from table %s\n", 
								table.c_str());
						goto exit_select;
					}
				}
				
				// Scan condition vectors to see if conditions are met for tuple
				bool cond_met = true;
				for (unsigned i = 0; i < reduced_key_cond.size(); i++) 
				{
					if(check_cond(key, value, reduced_key_cond[i]) == false) {
						cond_met = false;
						break;
					}
				}
				for (unsigned i = 0; i < reduced_val_cond.size(); i++) 
				{
					if(check_cond(key, value, reduced_val_cond[i]) == false) {
						cond_met = false;
						break;
					}
				}				
				
				// If all the conditions are true, output the value
				if (cond_met){
					// print the tuple based on what output cols are wanted
					switch (attr) 
					{
						case 1:  // SELECT key
							fprintf(stdout, "%d\n", key);
							break;
						case 2:  // SELECT value
							fprintf(stdout, "%s\n", value.c_str());
							break;
						case 3:  // SELECT *
							fprintf(stdout, "%d '%s'\n", key, value.c_str());
							break;
					}
					count++;
				}
				// If it is the end of the tree, then stop
				if(rfRC == RC_END_OF_TREE)
					break;
				// Otherwise key does not match
			}
			
		}
	}
	
	// Otherwise, use the old method
	// NOTE: Revised to use simplified conditions
	else
	{
		// scan the table file from the beginning
		rid.pid = rid.sid = 0;
		while (rid < rf.endRid()) 
		{
			// read the tuple
			if ((rc = rf.read(rid, key, value)) < 0) 
			{
				fprintf(stderr, "Error: while reading a tuple from table %s\n", 
						table.c_str());
				goto exit_select;
			}			
			
			// Scan condition vectors to see if conditions are met
			for (unsigned i = 0; i < reduced_key_cond.size(); i++) 
			{
				if(check_cond(key, value, reduced_key_cond[i]) == false)
					goto next_tuple;
			}
			for (unsigned i = 0; i < reduced_val_cond.size(); i++) 
			{
				if(check_cond(key, value, reduced_val_cond[i]) == false)
					goto next_tuple;
			}

			// If the condition is met for the tuple, increase the matching
			// tuple counter
			count++;

			// print the tuple 
			switch (attr) 
			{
				case 1:  // SELECT key
					fprintf(stdout, "%d\n", key);
					break;
				case 2:  // SELECT value
					fprintf(stdout, "%s\n", value.c_str());
					break;
				case 3:  // SELECT *
					fprintf(stdout, "%d '%s'\n", key, value.c_str());
					break;
			}

			// move to the next tuple
			next_tuple:
			++rid;
		}
	}

	// print matching tuple count if "select count(*)"
	if (attr == 4) 
	{
		fprintf(stdout, "%d\n", count);
	}
	rc = 0;

	// close the table file and return
	exit_select:
	rf.close();
	return rc;
}


// CS 143 Project 2
// Part A
// Implement the load() function to create a table from the filename
// Assumptions:
//		- Only use well-formed load files
// Methods:
//		- parseLoadLine(line, key, value)
//			takes a line from the load file, and returns it 
//			as a key-value pair
//		- fstream, fgets, etc.
//			use to read in data from the given file
//		- RecordFile class
//			method for storing BruinBase tables
RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	RC rc;
	// Open the table in write mode
	string table_name = table + ".tbl";		// Table file name
	RecordFile newTable;
	newTable.open(table_name, 'w');			// Open the table
	
	// Open the file for reading
	ifstream table_file;
	table_file.open(loadfile.c_str());		// Open the input file and check
	if(table_file.is_open())
	{
		
		// Prepare variables for reading
		SqlEngine parser;		// Parser
		string	table_line;		// Line in the table
		int		entry_key;		// Line's key
		string	entry_val;		// Line's value
		RecordId entry_id;		// Record ID
		
		// With indexing
		if(index) 
		{
			BTreeIndex newIndex;
			string indexName = table + ".idx";
			
			// Read in any existing index information for the table
			rc = newIndex.open(indexName, 'r');
			if(rc == 0)
				rc = newIndex.close();
      
			rc =newIndex.open(indexName, 'w');
			rc =newIndex.close();
        
			rc = newIndex.open(indexName, 'w');
			if(rc != 0) {
				newIndex.close();
				// Close the file
				table_file.close();
				// Close the table
				newTable.close();
				return rc;
			}
      
			// For all the lines in the file...
			while(table_file.good())
			{
				// Acquire and parse a line
				getline(table_file, table_line);
				if(parser.parseLoadLine(table_line, entry_key, entry_val) == 0)
				{
					// Store the line in the table
					newTable.append(entry_key, entry_val, entry_id);
          
					// Also insert the key into the index with the entry_id
					rc = newIndex.insert(entry_key, entry_id);
					if(rc != 0) {
						newIndex.close();
						// Close the file
						table_file.close();
						// Close the table
						newTable.close();
						return rc;
					}
          
					// Increment the record ID
					entry_id++;
				}
			}
			rc = newIndex.close();
			if(rc != 0) {
				// Close the file
				table_file.close();
				// Close the table
				newTable.close();
				return rc;
			}
		}
		
		// Without indexing
		else 
		{
			// For all the lines in the file...
			while(table_file.good())
			{
				// Acquire and parse a line
				getline(table_file, table_line);
				if(parser.parseLoadLine(table_line, entry_key, entry_val) == 0)
				{
					// Store the line in the table
					newTable.append(entry_key, entry_val, entry_id);
          
					// Increment the record ID
					entry_id++;
				}
			}
		}
	}
	
	// Close the file
	table_file.close();
	
	// Close the table
	newTable.close();

  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
